package org.zjx.task.core;

import lombok.Data;
import org.zjx.core.GameInstance;
import org.zjx.task.ChainContext;
import org.zjx.task.exception.RestartChainException;
import org.zjx.task.exception.TaskExecutionException;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;

@Data
public class TaskScheduler {
    private final BlockingDeque<ITaskChain> chainQueue = new LinkedBlockingDeque<>();
    private final Thread workerThread;

    private final AtomicBoolean isRunning = new AtomicBoolean(true);
    private final ReentrantLock queueLock = new ReentrantLock();

    private final ExecutorService asyncExecutor;
    private final List<CompletableFuture<?>> pendingAsyncTasks = new CopyOnWriteArrayList<>();

    private final AtomicReference<ITaskChain> currentChain = new AtomicReference<>();

    public static String NO_TASK_VALUE = "任务栈空";

    private GameInstance gameInstance;

    public TaskScheduler(int asyncPoolSize, GameInstance gameInstance) {
        this.asyncExecutor = Executors.newFixedThreadPool(asyncPoolSize);
        this.workerThread = new Thread(this::processChains);
        this.workerThread.start();
        this.gameInstance = gameInstance;
    }

    public void addChain(ITaskChain chain) {
        queueLock.lock();
        try {
            chainQueue.offerLast(chain);
        } finally {
            queueLock.unlock();
        }
    }

    public void addChainToHead(ITaskChain chain) {
        queueLock.lock();
        try {
            chainQueue.offerFirst(chain);
        } finally {
            queueLock.unlock();
        }
    }

    public void addChainToHead(List<ITaskChain> chains) {
        if (chains == null || chains.isEmpty()) return;
        queueLock.lock();
        try {
            Collections.reverse(chains);
            chains.forEach(chainQueue::offerFirst);
        } finally {
            queueLock.unlock();
        }
    }

    public void pause() {
        if (currentChain.get() != null) currentChain.get().pause();
    }

    public void resume() {
        if (currentChain.get() != null) currentChain.get().resume();
    }

    public void interrupt() {
        this.workerThread.interrupt();
    }

    /**
     * 处理任务链队列
     */
    private void processChains() {
        while (isRunning.get() && !Thread.currentThread().isInterrupted()) {
            try {
                ITaskChain chain = chainQueue.poll(100, TimeUnit.MILLISECONDS);
                if (chain == null) {
                    if (gameInstance.getGameContext() != null) {
                        gameInstance.getGameContext().notifyListeners(this.getClass().getSimpleName(), NO_TASK_VALUE);
                    }
                    continue;
                }
                currentChain.set(chain);
                try {
                    chain.execute(this);
                } catch (RestartChainException e) {
                    chainQueue.offerFirst(chain); // 重新入队
                }

            } catch (InterruptedException e) {
                if (isRunning.get()) {
                    Thread.currentThread().interrupt();
                    continue;
                }
                break;
            } catch (Exception e) {
                System.err.println(e.getMessage());
            } finally {
                currentChain.set(null);
            }
        }
    }

    public void executeTask(ITask task, ChainContext context) throws TaskExecutionException {
        switch (determineStrategy(task)) {
            case SYNC:
                waitAndExecuteSyncTask(task, context);
                break;
            case ASYNC:
                submitAsyncTask(task, context);
                break;
            default:
                throw new UnsupportedOperationException("Unsupported strategy");
        }
    }

    /**
     * 执行同步任务前等待
     */
    private void waitAndExecuteSyncTask(ITask syncTask, ChainContext context) throws TaskExecutionException {
        waitAllAsyncTasks();
        try {
            syncTask.run(context, this);
        } catch (TaskExecutionException e) {
            handleTaskFailure(syncTask, e);
            throw new TaskExecutionException(e.getMessage());
        }
    }

    /**
     * 提交异步任务（主流程用）
     */
    private void submitAsyncTask(ITask task, ChainContext context) {
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            try {
                task.run(context, this);
            } catch (TaskExecutionException e) {
                handleTaskFailure(task, e);
            }
        }, asyncExecutor);

        pendingAsyncTasks.add(future);
        future.whenComplete((r, e) -> pendingAsyncTasks.remove(future));
    }


    /**
     * 等待所有异步任务完成
     */
    public void waitAllAsyncTasks() {
        if (pendingAsyncTasks.isEmpty()) return;

        CompletableFuture<?>[] futures = pendingAsyncTasks.toArray(new CompletableFuture[0]);
        try {
            CompletableFuture.allOf(futures).get(30, TimeUnit.MINUTES);
        } catch (TimeoutException e) {
            System.err.println("等待异步任务超时，强制取消");
            pendingAsyncTasks.forEach(f -> f.cancel(true));
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.err.println("等待被中断");
        } catch (ExecutionException e) {
            System.err.println("异步任务执行异常: " + e.getCause().getMessage());
        }
        pendingAsyncTasks.clear();
    }

    private ITask.ExecutionStrategy determineStrategy(ITask task) {
        ITask.ExecutionStrategy strategy = task.getExecutionStrategy();
        return (strategy == ITask.ExecutionStrategy.AUTO) ? (task.isAsyncSupported() ? ITask.ExecutionStrategy.ASYNC : ITask.ExecutionStrategy.SYNC) : strategy;
    }

    private void handleTaskFailure(ITask task, TaskExecutionException e) {
        System.err.printf("[Task Failure] %s - %s%n", task.getTaskId(), e.getMessage());
    }

    public void shutdown() {
        isRunning.set(false);
        asyncExecutor.shutdownNow();
        workerThread.interrupt();
        try {
            if (!asyncExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
                asyncExecutor.shutdownNow();
            }
            if (workerThread.isAlive()) {
                workerThread.join(5000);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}
